package academic.ui.server;


import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.torque.NoRowsException;
import org.apache.torque.TooManyRowsException;
import org.apache.torque.TorqueException;
import org.apache.torque.util.Criteria;

import torque.generated.Creditcard;
import torque.generated.CreditcardPeer;
import torque.generated.Customers;
import torque.generated.CustomersPeer;
import torque.generated.Gifts;
import torque.generated.Orders;
import torque.generated.ProductsPeer;
import torque.generated.Zones;
import torque.generated.ZonesPeer;




import com.workingdogs.village.DataSetException;

import academic.ui.client.CheckoutService;
import academic.ui.client.rules.Rules;
import academic.ui.client.util.KKGWTException;
import academic.ui.client.util.KKGWTInvalidSessionException;
import academic.ui.server.payment.AuthorizeNet;
import academic.ui.server.soco.PaymentInfo;
import academic.ui.server.utils.QueryCardDetails;
import ecommerce.soco.server.encrypt.Encrypt;
import ecommerce.soco.server.util.email.EmailUsers;
import ecommerce.soco.shared.*;


public class CheckoutServiceImpl extends AppServiceImpl implements CheckoutService {
	/**
	 * 
	 */
	private static final long serialVersionUID = -4201915118871795793L;

	private boolean includeCVV = false;

	

	private Integer registerCustomer(GWT_Customer customer,
			Boolean loginIfUserExists) throws Exception {
		
		SocoEngine soco = this.getAppEng();
		soco.getUserMgr().login(customer.getEmailAddr(), customer.getPassword());
		if(!soco.getUserMgr().isLoggedIn())
		{
			soco.getUserMgr().registerCustomer(customer,soco.getCity());
		}
		else if(!loginIfUserExists)
		{
			return Rules.REGISTER_ERROR_USER_EXISTS;
		}
		
		if(soco.getUserMgr().isLoggedIn())
			return soco.getUserMgr().getCurrentCustomer().getCustomersId();
		else
			return Rules.LOGIN_ERROR_USER_NOT_FOUND;
	}

	
	  
	
		/**
		 * Method for updating card info before sending to the payment module
		 * in the event that the user has chosen a pre-existing card
		 * @param cCard return the same card if the id == -1,
		 * Return the card from the database if the id is not -1
		 * @return
		 */
		private GWT_CreditCard getSecureCardInfo(GWT_CreditCard cCard) {
			if(cCard.getCreditCardId()==-1) {
				includeCVV  = true;
				return cCard;
			}
			else
			{
				return getCreditCard(String.valueOf(cCard.getCreditCardId()));
			}
		}

	  
	   
	private static String code = "authorizenet";

	
	 @Override
		public Integer processOrder(Integer productID, Integer quantity,GWT_Customer customerRg,
				GWT_CreditCard cCard, GWT_Gift gift) {
			 try {
				 
				 SocoEngine socoEng = getAppEng();
				 includeCVV=false;
				 cCard = getSecureCardInfo(cCard);
				 		
				 Orders order = new Orders();
			    	if(!isLoggedIn())
					{//try to register/login the customer since there is not a session
						int customerRegId = registerCustomer(customerRg,/*login if the customer already exists*/ true);
						
						if(customerRegId==Rules.LOGIN_ERROR_USER_NOT_FOUND)
						{
							return Rules.LOGIN_ERROR_USER_NOT_FOUND;
						}
					}
			    order.setCustomers(socoEng.getUserMgr().getCurrentCustomer());
			    order.setProducts(ProductsPeer.retrieveByPK(productID));
			    order.setCurrencyValue(ProductsPeer.retrieveByPK(productID).getProductsPrice());
			    order.setCreditcard(helper.getCreditCard(cCard));
			    Boolean isGift = gift!=null && !"".equals(gift.getRecipientEmail());
			    if(isGift)
			    	order.setOrdersType("gift");
			    
			    PaymentInfo info =  AuthorizeNet.getPaymentDetails(order);

			          				 
			     order.setCreditcardId(cCard.getCreditCardId());
			     order.setOrdersQuantity(quantity);
			     order.setDatePurchased(new Date());
			     order.setOrdersStatus(0);
			     
		         Customers customer = order.getCustomers();
		         if(customer.getCustomersId()<=-1)
		        	 return Rules.LOGIN_ERROR_USER_NOT_FOUND;
		         cCard.setCustomersId(customer.getCustomersId());
				
		         
				order.setCurrency("USD");

		        List<NameValue> parmList = new ArrayList<NameValue>();
		        parmList.add(new NameValue(x_card_num, cCard.getCcNumber()));
		      
		        if(includeCVV)
		        	parmList.add(new NameValue(x_card_code, cCard.getCcCVV()));
		        
		        parmList.add(new NameValue(x_exp_date, cCard.getCcExpires()));

		        // Do the post
		        String gatewayResp = postData(info, parmList);
		        
		        
		        gatewayResp = URLDecoder.decode(gatewayResp, "UTF-8");
		        if (log.isDebugEnabled())
		        {
		            log.debug("Unformatted GatewayResp = \n" + gatewayResp);
		        }

		        String errorDesc = null;
		        String gatewayResult = null;
		        String transactionId = null;
		        
		        // Process the parameters sent in the callback
		        StringBuffer sb = new StringBuffer();
		        String[] parms = gatewayResp.split(",");
		        if (parms != null)
		        {
		            for (int i = 0; i < parms.length; i++)
		            {
		                String parm = parms[i];
		                sb.append(getRespDesc(i + 1));
		                sb.append("=");
		                sb.append(parm);
		                sb.append("\n");

		                if (i + 1 == respTextPosition)
		                {
		                    errorDesc = parm;
		                } else if (i + 1 == respCodePosition)
		                {
		                    gatewayResult = parm;
		                    //ipnHistory.setGatewayResult(parm);
		                } else if (i + 1 == txnIdPosition)
		                {
		                    //ipnHistory.setGatewayTransactionId(parm);
		                    transactionId = parm;
		                } else if (i + 1 == parms.length)
		                {
		                	cCard.setCcType(parm);
		                }
		            }
		        }

		        // Put the response in the ipnHistory record
		        //ipnHistory.setGatewayFullResponse(sb.toString());

		        if (log.isDebugEnabled())
		        {
		            log.debug("Formatted Authorize.net response data:");
		            log.debug(sb.toString());
		        }

		        // See if we need to send an email, by looking at the configuration
		       
		        
		        // Determine whether the request was successful or not.If successful, we update the
	            // inventory as well as changing the state of the order
	            if (gatewayResult != null && gatewayResult.equals(approved))
	            {
	            	//Add the payment details object to the db for the current user
	            	Creditcard serverCard = helper.getCreditCard(cCard);
	            	serverCard.setDateAdded(new Date());
	            	serverCard.setLastUsed(new Date());
	            	serverCard.save();
	            	//update the total number of purchases for the given product
	            	helper.updateProductTotal(productID,quantity);
	                
	                order.setCurrency("USD");
	                order.setDatePurchased(new Date());
	                order.setCreditcard(serverCard);
	                order.save();
	                order.setOrdersNumber(Encrypt.getHash(String.valueOf(order.getOrdersId())).substring(0, 16));
	                order.save();
	              
	                if(isGift)
	                {
	                	Gifts g = helper.getGift(gift, order);
	                	g.save();
	                }

                    EmailUsers.sendOrderConfirmationMail(order);
            
	                return Integer.parseInt(approved);
	            }
	            else if (gatewayResult != null
	                    && (gatewayResult.equals(declined) || gatewayResult.equals(error)))
	            {
	                String comment = ORDER_HISTORY_COMMENT_KO + errorDesc;
	                /*
	                kkAppEng.getEng().changeOrderStatus(kkAppEng.getSessionId(), serverOrder.getId(),
	                        com.konakart.bl.OrderMgr.PAYMENT_DECLINED_STATUS, 
	                        sendEmail, comment);
	                       */
	                // Save the ipnHistory
	              //  ipnHistory.setKonakartResultDescription(RET0_DESC);
	              //  ipnHistory.setKonakartResultId(RET0);
	             //   ipnHistory.setCustomerId(kkAppEng.getCustomerMgr().getCurrentCustomer().getId());
	            //    kkAppEng.getEng().saveIpnHistory(kkAppEng.getSessionId(), ipnHistory);

	              
	                return Integer.parseInt(declined);
	               
	            }
	            else
	            {
	                /*
	                 * We only get to here if there was an unknown response from the gateway
	                 */
	                String comment = RET1_DESC + gatewayResult;
	               // kkAppEng.getEng().changeOrderStatus(kkAppEng.getSessionId(), serverOrder.getId(),
	                //        com.konakart.bl.OrderMgr.PAYMENT_DECLINED_STATUS, /* customerNotified */
	               //         sendEmail, comment);

	                // Save the ipnHistory
	               // ipnHistory.setKonakartResultDescription(RET1_DESC + gatewayResult);
	              //  ipnHistory.setKonakartResultId(RET1);
	              //  ipnHistory.setCustomerId(kkAppEng.getCustomerMgr().getCurrentCustomer().getId());
	             //   kkAppEng.getEng().saveIpnHistory(kkAppEng.getSessionId(), ipnHistory);

	               
		            return Integer.parseInt(gatewayResult);	
	            }
	            
		        
			} 
				 catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			return null;
	    
		      
		}
	 
	 /**
	     * Authorize.net returns a response as delimiter separated variables. In order to make them
	     * readable, we tag each one with a desscription before saving in the ipnHistory table.
	     * 
	     * @param position
	     * @return Response Description
	     */
	    private String getRespDesc(int position)
	    {
	        String ret = "unknown";
	        switch (position)
	        {
	        case 1:
	            ret = "Response Code";
	            break;
	        case 2:
	            ret = "Response Subcode";
	            break;
	        case 3:
	            ret = "Response Reason Code";
	            break;
	        case 4:
	            ret = "Response Reason Text";
	            break;
	        case 5:
	            ret = "Approval Code";
	            break;
	        case 6:
	            ret = "AVS Result Code";
	            break;
	        case 7:
	            ret = "Transaction ID";
	            break;
	        case 8:
	            ret = "Invoice Number";
	            break;
	        case 9:
	            ret = "Description";
	            break;
	        case 10:
	            ret = "Amount";
	            break;
	        case 11:
	            ret = "Method";
	            break;
	        case 12:
	            ret = "Transaction Type";
	            break;
	        case 13:
	            ret = "Customer ID";
	            break;
	        case 14:
	            ret = "Cardholder First Name";
	            break;
	        case 15:
	            ret = "Cardholder Last Name";
	            break;
	        case 16:
	            ret = "Company";
	            break;
	        case 17:
	            ret = "Billing Address";
	            break;
	        case 18:
	            ret = "City";
	            break;
	        case 19:
	            ret = "State";
	            break;
	        case 20:
	            ret = "Zip";
	            break;
	        case 21:
	            ret = "Country";
	            break;
	        case 22:
	            ret = "Phone";
	            break;
	        case 23:
	            ret = "Fax";
	            break;
	        case 24:
	            ret = "Email";
	            break;
	        case 25:
	            ret = "Ship to First Name";
	            break;
	        case 26:
	            ret = "Ship to Last Name";
	            break;
	        case 27:
	            ret = "Ship to Company";
	            break;
	        case 28:
	            ret = "Ship to Address";
	            break;
	        case 29:
	            ret = "Ship to City";
	            break;
	        case 30:
	            ret = "Ship to State";
	            break;
	        case 31:
	            ret = "Ship to Zip";
	            break;
	        case 32:
	            ret = "Ship to Country";
	            break;
	        case 33:
	            ret = "Tax Amount";
	            break;
	        case 34:
	            ret = "Duty Amount";
	            break;
	        case 35:
	            ret = "Freight Amount";
	            break;
	        case 36:
	            ret = "Tax Exempt Flag";
	            break;
	        case 37:
	            ret = "PO Number";
	            break;
	        case 38:
	            ret = "MD5 Hash";
	            break;
	        case 39:
	            ret = "(CVV2/CVC2/CID)Response Code";
	            break;
	        case 40:
	            ret = "(CAVV) Response Code";
	            break;
	        default:
	            break;
	        }

	        return ret;
	    }

	
	 // Authorize.net constants for request

    private static final String x_card_num = "x_card_num";

    private static final String x_exp_date = "x_exp_date";

    private static final String x_card_code = "x_card_code";
    
    // Authorize.net constants for response
    private static final int respCodePosition = 1;

    private static final int respTextPosition = 4;

    private static final int txnIdPosition = 7;
    
    private static final String approved = "1";

    private static final String declined = "2";

    private static final String error = "3";
    
    // Return codes and descriptions
    private static final int RET0 = 0;

    private static final String RET0_DESC = "Transaction OK";

    private static final int RET1 = -1;

    private static final String RET1_DESC = "There was an unexpected Gateway Response. Response = ";

    private static final int RET4 = -4;

    private static final String RET4_DESC = "There was an unexpected exception. Exception message = ";

    // Order history comments. These comments are associated with the order.
    private static final String ORDER_HISTORY_COMMENT_OK = "Authorize.net payment successful. Authorize.net TransactionId = ";

    private static final String ORDER_HISTORY_COMMENT_KO = "Authorize.net payment not successful. Authorize.net Reply = ";
    

	@Override
	public GWT_CreditCard[] addCard(GWT_CreditCard card) {
				card.setCustomersId(getAppEng().getUserMgr().getCurrentCustomer().getCustomersId());
            	try {
            		if(cardExistsForUser(card))
            			return null;
            		else
            		{//let's assume that since the user is adding this card they would like for it to become their default
            			card.setIsDefault(true);
            			unsetOtherCards(card.getCreditCardId());
            			helper.getCreditCard(card).save();
            		}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
            	return getUserPaymentOptions();
	}


	private boolean cardExistsForUser(GWT_CreditCard card) {
		try {
			List<Creditcard> cards = CreditcardPeer.doSelect(new Criteria().add(CreditcardPeer.CCNUMBER,card.getCcNumber()).and(CreditcardPeer.CUSTOMERS_ID,card.getCustomersId()));
			return cards!=null && cards.size()>0;
		} catch (TorqueException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
	}




	@Override
	public GWT_Zone[] getZones() {
		
           // Default 223 Country ID For United States
			Criteria criteria = new Criteria();
			criteria.add(ZonesPeer.COUNTRY_CODE,223);
			List<Zones> zones;
			try {
				zones = ZonesPeer.doSelect(criteria);
			
            GWT_Zone[] clientZones = new GWT_Zone[zones.size()];
            for(int i=0;i<zones.size();i++)
            {
            	GWT_Zone clientZone = helper.getGWT_Zone(zones.get(i));
            	clientZones[i] = clientZone;
            }
            return clientZones;
            
			} catch (TorqueException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
	}

	@Override
	public GWT_CreditCard[] getUserPaymentOptions()  {
		GWT_CreditCard[] pdetails = null;
		if(isLoggedIn())
		{
			Criteria c = new Criteria();
			c.add(CreditcardPeer.CUSTOMERS_ID, getAppEng().getUserMgr().getCurrentCustomer().getCustomersId());
			c.and(CreditcardPeer.STATUS,"active");
			try {
				List<Creditcard> creditCards = CreditcardPeer.doSelect(c);
				
				pdetails = new GWT_CreditCard[creditCards.size()];
				for(int i=0;i<creditCards.size();i++)
				{
					pdetails[i] = helper.getGWT_CreditCard(creditCards.get(i));
				}
			} catch (TorqueException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		
		return pdetails;
	}
	

	@Override
	public Integer updateCard(GWT_CreditCard creditCard)  {
		try {
			creditCard.setCustomersId(getAppEng().getUserMgr().getCurrentCustomer().getCustomersId());
			Creditcard card = helper.getCreditCard(creditCard);
			
			//assume that this card will be the new default b/c the user is changing it
			card.setIsDefault("Y");
			unsetOtherCards(creditCard.getCreditCardId());
			Creditcard dbCard = CreditcardPeer.retrieveByPK(creditCard.getCreditCardId());
			card.setDateAdded(dbCard.getDateAdded());
			card.setLastUsed(dbCard.getLastUsed());
			if(card.getCcnumber()==null)
				card.setCcnumber(dbCard.getCcnumber());
			if(card.getCctype()==null)
				card.setCctype(dbCard.getCctype());
			card.save();
			
			return card.getCreditcardId();
		} catch (TorqueException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return -1;
	}

	/**
	 * unset is_default property of every card that does not have the given id for the current user
	 * @param creditCardId
	 */
	private void unsetOtherCards(int creditCardId) {
		try {
			CreditcardPeer.doUpdate(new Criteria().add(CreditcardPeer.CUSTOMERS_ID,getAppEng().getUserMgr().getCurrentCustomer().getCustomersId()).and(CreditcardPeer.CREDITCARD_ID,creditCardId,Criteria.NOT_EQUAL),new Criteria().add(CreditcardPeer.IS_DEFAULT,"N"));
			
		} catch (TorqueException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}




	@Override
	public GWT_CreditCard getCreditCard(String cardID) {
		
		try {
			return helper.getGWT_CreditCard(CreditcardPeer.retrieveByPK(Integer.parseInt(cardID)));
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TorqueException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		return null;
	}




	@Override
	public Boolean setAsDefaultCard(String id) {
		unsetOtherCards(Integer.parseInt(id));
		try {
			CreditcardPeer.doUpdate(new Criteria().add(CreditcardPeer.CREDITCARD_ID,Integer.parseInt(id)),new Criteria().add(CreditcardPeer.IS_DEFAULT,"Y"));
			return true;
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TorqueException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}



	/**
	 * remove the card with the given ID.  If the card happens to be the default
	 * then set the new default to be a different card within the users cards based
	 * on last_used date
	 */
	@Override
	public GWT_CreditCard[] removeCard(String id) {
		try {
			Creditcard deleteCard = CreditcardPeer.retrieveByPK(Integer.parseInt(id));
			if("Y".equals(deleteCard.getIsDefault()))
			{//need to set a new default
				List<Creditcard> otherCards = CreditcardPeer.doSelect(new Criteria().add(CreditcardPeer.IS_DEFAULT,"N").and(CreditcardPeer.CUSTOMERS_ID,getAppEng().getUserMgr().getCurrentCustomer().getCustomersId()).addDescendingOrderByColumn(CreditcardPeer.LAST_USED));
				if(otherCards!=null && otherCards.size()>0)
				{
					Creditcard newDefaultCard =otherCards.get(0);
					newDefaultCard.setIsDefault("Y");
					newDefaultCard.save();
				}
			}
			//now that we have a new default we can delete the card
			deleteCard.setStatus("deleted");
			CreditcardPeer.doUpdate(deleteCard);
			
			//with the card now deleted return the new set of credit cards for the given user
			return getUserPaymentOptions();
		} catch (NoRowsException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TooManyRowsException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TorqueException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return null;
	}


}
